// cloudfunctions/virtualTryOn/index.js
// 详细说明与使用方法（注释很长，请阅读）
// 功能概要：
//  - 接收前端传入的 cloud fileID（自拍 selfieFileID）和若干商品图片的 fileIDs（productFileIDs）
//  - 将这些 cloud file 下载到云函数临时目录，组装 multipart/form-data 请求并转发给第三方 AI（例如“元宝”VTO）
//  - 解析第三方返回（常见为 imageBase64 或可访问的 fileUrl），并把生成的图片保存回微信云存储（得到 fileID）
//  - 返回统一结构给前端：{ success: true, results: [{ fileId, cloudPath }|{fileUrl}|{error}], ... }

// 使用前准备：
// 1) 在云函数的环境变量中配置：
//    VTO_API_URL - 第三方 VTO 接口的 HTTP 地址（例如：https://api.yourvto.com/generate）
//    VTO_API_KEY - API Key 或 Bearer Token，用于鉴权（不要把该值放到前端）
// 2) 在微信开发者工具 -> 云函数 -> 选择本函数，右键 "上传并部署"，以确保 package.json 中的 axios/form-data 依赖被安装。
// 3) 根据第三方 API 文档，可能需要调整 form 字段名、headers 或额外参数（options 已提供字段可传）。

// 参数约定（前端调用时请遵循）：
// wx.cloud.callFunction({
//   name: 'virtualTryOn',
//   data: {
//     selfieFileID: '<cloud://.../aaa.jpg>',
//     productFileIDs: ['<cloud://.../p1.jpg>', '<cloud://.../p2.jpg>'],
//     options: { style: 'casual', size: '1024' }, // 可选，按第三方要求定制
//     _debug: true // 可选，开发时启用以返回更多错误信息
//   }
// })

// 返回示例（同步成功）：
// { success: true, results: [ { fileId: 'cloud://xxx/virtual_tryon_results/..', cloudPath: 'virtual_tryon_results/..' }, ... ] }
// 失败示例：
// { success: false, message: 'VTO API 未配置 (VTO_API_URL / VTO_API_KEY)' }

// 限制与建议：
// - 云函数执行有超时时间限制（默认 60s，可在控制台调整），若第三方生成耗时较长，建议采用异步任务（入库并异步处理，前端轮询或使用 DB 订阅）。
// - 限制上传图片大小和数量（例如单张 <= 4MB，总数 <= 5 张），避免超时和高额流量。
// - 第三方返回若为外网 URL，注意是否可被小程序直接访问（通常需要 https）。

// 错误排查要点：
// - 在云函数控制台查看日志（console.log/console.error），会输出下载/调用/上传的关键错误。
// - 如果 cloud.uploadFile({ fileContent }) 报错，函数已实现回退：会把 Buffer 写入 /tmp 临时文件并用 filePath 上传。
// - 若第三方返回格式与这里假定不同（非 results 或字段名不同），需按返回结构修改解析逻辑。

// 下面是函数实现（请阅读每一步注释）：

const cloud = require('wx-server-sdk')
const fs = require('fs')
const path = require('path')
const axios = require('axios')
const FormData = require('form-data')

cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV })

// Depends on tencentcloud-sdk-nodejs version 4.0.3 or higher

const tencentcloud = require("tencentcloud-sdk-nodejs-aiart");

const AiartClient = tencentcloud.aiart.v20221229.Client;

// 密钥信息从环境变量读取，需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY
// 使用环境变量方式可以避免密钥硬编码在代码中，提高安全性
// 生产环境建议使用更安全的密钥管理方案，如密钥管理系统(KMS)、容器密钥注入等
// 请参见：https://cloud.tencent.com/document/product/1278/85305
// 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取
const clientConfig = {
  credential: {
    secretId: process.env.TENCENTCLOUD_SECRET_ID,
    secretKey: process.env.TENCENTCLOUD_SECRET_KEY,
  },
// 使用临时密钥示例
/*
  credential: {
    secretId: "SecretId",
    secretKey: "SecretKey",
    token: "Token",
  }
*/
  region: "ap-guangzhou",
  profile: {
    httpProfile: {
      endpoint: "aiart.tencentcloudapi.com",
    },
  },
};

// 实例化要请求产品的client对象,clientProfile是可选的
const client = new AiartClient(clientConfig);
const params = {
    "ModelUrl": "https://cos.ap-guangzhou.myqcloud.com/model.jpg",
    "ClothesUrl": "https://cos.ap-guangzhou.myqcloud.com/cloth.jpg",
    "ClothesType": "Upper-body",
    "LogoAdd": 0,
    "RspImgType": "url"
};
client.ChangeClothes(params).then(
  (data) => {
    console.log(data);
  },
  (err) => {
    console.error("error", err);
  }
);

exports.main = async (event, context) => {
  const { selfieFileID, productFileIDs = [], options = {}, _debug } = event || {}
  const wxContext = cloud.getWXContext()
  const openid = wxContext.OPENID || 'anonymous'

  if (!selfieFileID || !Array.isArray(productFileIDs) || productFileIDs.length === 0) {
    return { success: false, message: '缺少 selfie 或 product 图片' }
  }

  try {
  // ---------- 1) 下载前端上传到云存储的文件到云函数临时路径 ----------
  // cloud.downloadFile({ fileID }) 会返回 { tempFilePath }
  // 该 tempFilePath 在云函数运行环境（沙箱）内可读，可以作为 fs.createReadStream 的输入
  // 注意：下载失败会抛异常，应在 try/catch 中捕获并返回友好错误信息
  const selfieDl = await cloud.downloadFile({ fileID: selfieFileID })
  const selfieTempPath = selfieDl.tempFilePath

    // 下载产品图片
    const productDownloads = await Promise.all(productFileIDs.map(fid => cloud.downloadFile({ fileID: fid })))
    const productTempPaths = productDownloads.map(d => d.tempFilePath)

  // ---------- 2) 使用 form-data 构造 multipart/form-data 请求体 ----------
    // 使用第三方 API 时，很多接口接受 multipart 上传图片（类似浏览器表单上传）
    // 这里把 selfie 和每张 product 图片以文件流方式附加到 form 中
    // options 字段用于向第三方传递额外参数（如风格、尺寸等），以 JSON 字符串形式发送
    const form = new FormData()
    form.append('selfie', fs.createReadStream(selfieTempPath))
    productTempPaths.forEach((p, idx) => {
      // 字段名按第三方 API 要求命名（这里使用 product_0, product_1 ... 作为示例）
      form.append(`product_${idx}`, fs.createReadStream(p))
    })
    form.append('options', JSON.stringify(options || {}))
    // 如果前端传来了 clothesType（Upper-body / Lower-body / Dress），也把它带上
    const clothesType = event.clothesType || options.clothesType || ''
    if (clothesType) {
      form.append('clothesType', clothesType)
    }

    // 第三方 API 配置（请在云函数环境变量中配置）
    // 支持把 clothesType 做为 query 参数传递，方便某些 API 以 URL 路径或 query 区分
    const API_URL_BASE = process.env.VTO_API_URL || ''
    const clothesQuery = clothesType ? `?clothesType=${encodeURIComponent(clothesType)}` : ''
    const API_URL = API_URL_BASE + clothesQuery
    const API_KEY = process.env.VTO_API_KEY || ''
    if (!API_URL || !API_KEY) {
      return { success: false, message: 'VTO API 未配置 (VTO_API_URL / VTO_API_KEY)' }
    }

    // ---------- 3) 把表单提交到第三方 VTO API ----------
    // headers 需要合并 form.getHeaders()（包含 Content-Type: multipart/form-data; boundary=...）
    // 此处同时添加 Authorization 头（Bearer token），以 VTO_API_KEY 做鉴权示例
    // timeout 设置为 120s，根据第三方实际耗时调整；若请求可能超时请考虑异步处理
    // 在发起请求前记录一下调用信息，便于在控制台查看是哪种衣服类型
    console.log('calling VTO API', { apiUrl: API_URL, clothesType })
    const resp = await axios.post(API_URL, form, {
      headers: Object.assign({ Authorization: `Bearer ${API_KEY}` }, form.getHeaders()),
      timeout: 120000 // 2 分钟
    })

    // ---------- 4) 解析第三方返回 ----------
    // 第三方可能返回如下几类格式：
    //  - 直接返回可访问的图片 URL 列表（fileUrl）
    //  - 返回 base64 编码的图片数据（imageBase64）
    //  - 返回压缩包（zip），需要额外的解压逻辑
    // 这里示例以 data.results 数组为准，具体字段请根据第三方文档调整
    const data = resp.data || {}
    const results = data.results || []
    if (!Array.isArray(results) || results.length === 0) {
      // 把原始响应放到 detail 中有助于排查（开发环境可打印完整数据）
      return { success: false, message: '第三方返回格式异常', detail: data }
    }

    // 把每个 base64 图片上传到云存储
    const uploadPromises = results.map(async (r, idx) => {
      // 支持两种返回：直接提供 fileUrl 或 base64
      if (r.fileUrl) {
        // 如果第三方返回可访问 URL，可以直接返回给前端或选择下载再上传
        return { fileUrl: r.fileUrl }
      }

      if (!r.imageBase64) {
        return { error: 'missing imageBase64', meta: r }
      }

  // ---------- 5) 把 base64 转成 Buffer 并上传回微信云存储 ----------
  // 这里把第三方返回的 imageBase64 解码成 Buffer，然后上传到云存储
  // cloudPath 可按需命名，建议包含 openid 与时间戳，方便后续清理与归属
  const buf = Buffer.from(r.imageBase64, 'base64')
  const cloudPath = `virtual_tryon_results/${openid}/${Date.now()}_${idx}.jpg`
      // 一些 SDK 版本不支持直接传 Buffer（fileContent），此处优先尝试 fileContent，失败则写临时文件再上传
      try {
        const uploadRes = await cloud.uploadFile({ cloudPath, fileContent: buf })
        return { fileId: uploadRes.fileID, cloudPath }
      } catch (e) {
        // fallback: 某些 SDK 版本不支持 fileContent 参数，这时写入临时文件再用 filePath 上传
        try {
          const tmpPath = `/tmp/${Date.now()}_${idx}.jpg`
          fs.writeFileSync(tmpPath, buf)
          const uploadRes2 = await cloud.uploadFile({ cloudPath, filePath: tmpPath })
          // 上传完成后建议删除临时文件，注意捕获异常以免影响主流程
          try { fs.unlinkSync(tmpPath) } catch (ignore) {}
          return { fileId: uploadRes2.fileID, cloudPath }
        } catch (e2) {
          console.error('upload fallback failed', e2)
          // 返回错误信息给前端，便于调试
          return { error: 'upload_failed', meta: e2.toString() }
        }
      }
    })

    // 等待所有上传完成并把结果返回前端
    const uploaded = await Promise.all(uploadPromises)
    return { success: true, results: uploaded }
  } catch (err) {
    // 捕获任意一步的异常并写日志，_debug 开关可在开发时返回详细错误信息给前端
    console.error('virtualTryOn error:', err)
    return { success: false, message: '生成失败', error: _debug ? (err.stack || err.message) : undefined }
  }
}
